#region Using directives

using System;
using System.IO;
using System.Text;
using System.Collections.Generic;

using Weazel.Gezel.Model;
using Weazel.Gezel.Parser;
using Weazel.Gezel.Simulator.Reference.Profiling;

#endregion

namespace Weazel.Gezel.Simulator.Reference
{
    /// <remarks>
    /// Enumerates the modes the simulation can be in.
    /// </remarks>
    public enum SimulationMode { Active, Sleeping };

    /// <summary>
    /// This is the root object of a Gezel simulation which
    /// stores all other objects which are part of a simulation
    /// run. Several instances are allowed to run in parallel.
    /// </summary>
    public class Simulation
    {
        // globals
        internal bool AllowSimulatorSleep = false;
        internal SimulationMode simulationMode;
        internal Sleep sleep = new Sleep();
        internal ValueChangeDump dump;
        internal Profiler profiler;

        internal bool debugMode = false;
        public bool DebugMode
        {
            get
            {
                return debugMode;
            }
            set
            {
                debugMode = value;
                if (value)
                    dump = new ValueChangeDump("TRACE.vcd");
            }
        }

        internal readonly Dictionary<Symid, Datapath> datapaths;
        public int DatapathCount
        {
            get
            {
                return datapaths.Values.Count;
            }
        }

        internal readonly Dictionary<Symid, AbstractSignal> signals;
        public int SignalCount
        {
            get
            {
                return signals.Values.Count;
            }
        }

        internal readonly Dictionary<Symid, IDirective> directives;
        public int DirectiveCount
        {
            get
            {
                return directives.Values.Count;
            }
        }
        internal readonly Dictionary<Symid, AbstractController> controllers;
        public int ControllerCount
        {
            get
            {
                return controllers.Values.Count;
            }
        }
        internal readonly Dictionary<Symid, LookupTable> lookupTables;
        public int LookupTableCount
        {
            get
            {
                return lookupTables.Values.Count;
            }
        }

        private readonly Dictionary<Symid, AbstractIpBlock> ipBlocks;

        public Dictionary<Symid, AbstractIpBlock> IpBlocks
        {
            get { return ipBlocks; }
        }

        public int IpblockCount
        {
            get
            {
                return ipBlocks.Values.Count;
            }
        }

        internal readonly Dictionary<Symid, TracerBlock> tracers;
        public int TracerCount
        {
            get
            {
                return tracers.Values.Count;
            }
        }

        /// <summary>
        /// Maps from symbol identifiers to the names from 
        /// the gezel model description
        /// </summary>
        internal readonly Dictionary<Symid, string> symbolNames;

        /// <summary>
        /// Records the current simulation cycle
        /// </summary>
        private long cycle;
        public long Cycle
        {
            get
            {
                return cycle;
            }
            internal set
            {
                cycle = value;
            }
        }

        internal long SleepCycle;

        private bool shouldFinish;

        private TextWriter outputWriter = Console.Out;
        public TextWriter OutputWriter
        {
            get { return outputWriter; }
            set { outputWriter = value; }
        }

        /// <summary>
        /// Buffers simulation output
        /// </summary>
        internal readonly StringBuilder buffer;

        /// <summary>
        /// Records the time the simulation run 
        /// was started
        /// </summary>
        private DateTime simulationStart;
        public DateTime SimulationStart
        {
            get
            {
                return simulationStart;
            }
        }

        /// <summary>
        /// Records the time the simulation run 
        /// finished
        /// </summary>
        private DateTime simulationEnd;
        public DateTime SimulationEnd
        {
            get
            {
                return simulationEnd;
            }
        }

        /// <summary>
        /// Simulation constructor
        /// </summary>
        public Simulation()
        {
            this.datapaths = new Dictionary<Symid, Datapath>();
            this.signals = new Dictionary<Symid, AbstractSignal>();
            this.directives = new Dictionary<Symid, IDirective>();
            this.controllers = new Dictionary<Symid, AbstractController>();
            this.lookupTables = new Dictionary<Symid, LookupTable>();
            this.ipBlocks = new Dictionary<Symid, AbstractIpBlock>();
            this.tracers = new Dictionary<Symid, TracerBlock>();
            this.symbolNames = new Dictionary<Symid, string>();
            this.buffer = new StringBuilder();
        }

        /// <summary>
        /// Performs a reset and and runs the simulation 
        /// for specified number of cycles.
        /// </summary>
        /// <param name="cycles"></param>
        public SimulationResult SetupAndRun(Model.Model model, long cycles)
        {
            try
            {
                Weazel.Gezel.Simulator.Reference.Setup.SimulationSetup.SetupFromModel(this, model);

            }
            catch (Exception e)
            {
                // simulation failed with an exception
                return new SimulationResult(new TimeSpan(), cycle, e);
            }

            Reset();

            SimulationResult result = Run(cycles);

            CleanUp();

            return result;
        }

        /// <summary>
        /// Sets up the simulation but does not run it.
        /// Run and CleanUp must be called manually.
        /// 
        /// Note: Callee must handle any exceptions thrown
        /// by the setup logic.
        /// </summary>
        public void Setup(Model.Model model)
        {
            Weazel.Gezel.Simulator.Reference.Setup.SimulationSetup.SetupFromModel(this, model);
            Reset();
        }

        public void FlushOutput()
        {
            outputWriter.Write(buffer);

            // reset output buffer
            buffer.Remove(0, buffer.Length);
        }

        /// <summary>
        /// Runs the simulation for the specified number of cycles
        /// </summary>
        /// <param name="cycles"></param>
        public SimulationResult Run(long cycles)
        {
            simulationStart = DateTime.Now;

            try
            {
                for (long c = 1; c <= cycles && !shouldFinish; ++c)
                {
                    step();

                    // don't always print to console
                    if (c % 10 == 0)
                    {
                        FlushOutput();
                    }
                }

                outputWriter.Write(buffer);
            }
            catch (Exception e)
            {
                simulationEnd = DateTime.Now;

                // flush the output
                FlushOutput();

                // simulation failed with an exception
                return new SimulationResult(simulationEnd - simulationStart, cycle, e);
            }

            simulationEnd = DateTime.Now;

            // simulation succeded 
            return new SimulationResult(simulationEnd - simulationStart, cycle);
        }

        /// <summary>
        /// Resets the simulation
        /// </summary>
        public void Reset()
        {
            shouldFinish = false;
            cycle = 0;
            SleepCycle = 0;
            simulationMode = SimulationMode.Active;

            if (dump != null)
                dump.Evaluate(cycle);

            buffer.Remove(0, buffer.Length); // reset output buffer
            foreach (AbstractController ctrl in controllers.Values)
            {
                ctrl.Reset();
            }
        }

        public void CleanUp()
        {
            // close all streams in tracerblocks
            foreach (TracerBlock tb in tracers.Values)
            {
                tb.CloseStream();
            }

            // Close VCD if any
            if (dump != null)
                dump.CloseStream();
        }

        /// <summary>
        /// The algorithm for running a single step of the simulation.
        /// </summary>
        public void step()
        {
            cycle++;

            if (debugMode)
            {
                if (simulationMode == SimulationMode.Active)
                {
                    Console.Error.WriteLine("> Cycle " + cycle);
                }
            }

            if (simulationMode == SimulationMode.Sleeping)
            {
                throw new NotImplementedException();
            }

            if (simulationMode == SimulationMode.Active)
            {
                //sleep.reset();

                // let each controller determine the set of sfg which is
                // active for this cycle
                foreach (AbstractController controller in controllers.Values)
                {
                    controller.RunNextStep(cycle);
                }

                // drive output and registers using the active sfg
                foreach (Datapath datapath in datapaths.Values)
                {
                    datapath.Evaluate(cycle);
                }

                foreach (AbstractIpBlock ipblock in ipBlocks.Values)
                {
                    ipblock.Evaluate(cycle, true);
                }

                // display & finish        
                foreach (IDirective dir in directives.Values)
                {
                    if (dir.CanExecute())
                        dir.Execute(cycle, buffer);
                }

                // call tracer blocks
                foreach (TracerBlock tracer in tracers.Values)
                {
                    tracer.Evaluate(cycle);
                }

                // Call ValueChangeDump if enabled
                if (dump != null)
                    dump.Evaluate(cycle);

                // tick
                foreach (Datapath datapath in datapaths.Values)
                {
                    datapath.Tick();
                }
            }
        }

        /// <summary>
        /// Called from FinishDirective.Execute
        /// </summary>
        public void Finish()
        {
            shouldFinish = true;
        }

        public void WriteToggleCounts()
        {
            if(profiler != null)
                profiler.WriteToggleCounts(outputWriter);
        }
    }
}